# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: scanners\wvs\wvs_process.py
"""Module for running WVS processes"""
import os, logging, subprocess
from subprocess import TimeoutExpired, SubprocessError
import time, glob, platform
from helpers.address import encode_url
from settings_provider import settings

__author__ = "r4w8173"
_module_log = logging.getLogger("wvs_process")


class WVSProcessError(Exception):
    """Exception indicating that there was a problem running WVS"""

    pass


class WVSExecutionError(WVSProcessError):
    """unable to start WVS with Popen"""

    pass


class WVSNotFoundError(WVSProcessError):
    """wvs was not found in the configured path"""

    pass


class WVSLicenseTimeout(WVSProcessError):
    """there was a timeout while reading WVS license"""

    pass


class ProcessAlreadyRunning(WVSProcessError):
    """the process is already started"""

    pass


class WVSExitCode:
    """Predefined exit codes of WVSC.EXE process."""

    NO_ERROR = 0
    CRASH = 100
    INIT_PROBLEM = 200
    INVALID_COMMAND_LINE = 300
    LICENSE_PROBLEM = 400


class WVSProcess:
    """Represents a WVS process. Can be used to launch WVS web scans."""

    WVS_EXECUTABLE = "wvsc"
    SETTINGS_NAME = "settings.xml"
    STATUS_API = None
    SAVEFILE_NAME = None
    LOG_LEVEL = "info"
    LOGFILE_NAME = "logfile.csv"

    def refresh_wvs_license(self, timeout=5):
        args = [os.path.join(self.wvs_directory, self.wvs_executable), "/lic"]
        if not os.path.isfile(args[0]):
            _module_log.error("Wvs not found at %s", args[0])
            raise WVSNotFoundError()
        try:
            process = subprocess.Popen(
                args,
                cwd=self.wvs_directory,
                creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
                stderr=subprocess.DEVNULL,
                stdout=subprocess.DEVNULL,
            )
            process.communicate(timeout=timeout)
        except TimeoutExpired:
            raise WVSLicenseTimeout()
        except SubprocessError:
            raise WVSExecutionError()

    def __init__(
        self,
        wvs_directory,
        wvs_settings,
        working_directory,
        profile=None,
        logfile_name=None,
        log_level=None,
        savefile_name=None,
        status_api=None,
        status_api_key=None,
        settings_name=None,
        crashdump_dir=".",
        demo_mode=False,
        initial_state_file_name=None,
    ):
        """Create a WVS process. wvs_settings and working_directory is mandatory for the rest of
        the parameters the Class constants will be used
        """
        self.wvs_directory = wvs_directory
        self.wvs_settings = wvs_settings
        self.working_directory = working_directory
        self.logfile_name = (
            logfile_name if logfile_name is not None else self.LOGFILE_NAME
        )
        self.log_level = log_level if log_level is not None else self.LOG_LEVEL
        self.savefile_name = (
            savefile_name if savefile_name is not None else self.SAVEFILE_NAME
        )
        self.status_api = status_api if status_api is not None else self.STATUS_API
        self.settings_name = (
            settings_name if settings_name is not None else self.SETTINGS_NAME
        )
        self.crashdump_dir = crashdump_dir
        self.status_api_key = status_api_key
        self.profile = profile
        self._process = None
        self._ctrl_break_time = None
        self._ctrl_c_time = None
        self.stderr = None
        self.exit_code = None
        self.debug = False
        self.demo_mode = demo_mode
        self.initial_state_file_name = initial_state_file_name
        self.wvs_executable = self.WVS_EXECUTABLE
        if platform.system() == "Windows":
            if not self.wvs_executable.endswith(".exe"):
                self.wvs_executable += ".exe"

    def get_filepath(self, filename):
        """Returns the absolute path of a file relative to the working_directory"""
        if filename is None:
            return
        else:
            return os.path.abspath(os.path.join(self.working_directory, filename))

    def create_working_directory(self):
        """Creates working directory if not exists"""
        if not os.path.isdir(self.working_directory):
            os.makedirs(self.working_directory)

    def start_scanning(self):
        """Launches WVSC.EXE with the specified parameters"""
        if self._process is not None:
            raise ProcessAlreadyRunning()
        executable = os.path.join(self.wvs_directory, self.wvs_executable)
        arguments = [
            executable,
            "/scan",
            encode_url(self.wvs_settings.get_target_url()),
        ]
        if self.demo_mode:
            arguments.append("/demo")
        if not os.path.isfile(arguments[0]):
            _module_log.error("Wvs not found at %s", arguments[0])
            raise WVSNotFoundError()
        self.create_working_directory()
        if self.settings_name:
            settings_filename = self.get_filepath(self.settings_name)
            arguments += ["/settings", settings_filename]
            self.wvs_settings.create_settings_xml_file(settings_filename)
        if self.crashdump_dir:
            arguments += ["/dmpdir", self.crashdump_dir]
        if self.logfile_name is not None:
            arguments += ["/log", self.get_filepath(self.logfile_name)]
            if self.log_level is not None:
                arguments += ["/log-level", self.log_level]
        if self.savefile_name is not None:
            arguments += ["/save", self.get_filepath(self.savefile_name)]
        if self.initial_state_file_name is not None:
            if not settings.get("features.engine_v11"):
                fn = self.get_filepath(self.initial_state_file_name)
                if os.path.exists(fn):
                    arguments += [
                        "/load",
                        self.get_filepath(self.initial_state_file_name),
                    ]
        if self.profile is not None:
            arguments += ["/profile", self.profile]
        if self.status_api.startswith("http://") or self.status_api.startswith(
            "https://"
        ):
            arguments += ["/status", self.status_api]
            if self.status_api_key is not None:
                arguments += ["/api-key", self.status_api_key]
            else:
                arguments += ["/status", self.get_filepath(self.status_api)]
            if self.debug:
                arguments += ["/debug-info"]
            try:
                try:
                    from psutil import (
                        Process,
                        NORMAL_PRIORITY_CLASS,
                        BELOW_NORMAL_PRIORITY_CLASS,
                        IDLE_PRIORITY_CLASS,
                    )

                    Process().nice(IDLE_PRIORITY_CLASS)
                except ModuleNotFoundError:
                    pass

                _module_log.debug("EXEC %s", subprocess.list2cmdline(arguments))
                self._process = subprocess.Popen(
                    args=arguments,
                    cwd=self.working_directory,
                    creationflags=getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0),
                    stderr=subprocess.PIPE,
                    stdout=subprocess.DEVNULL,
                )
                try:
                    from psutil import (
                        Process,
                        NORMAL_PRIORITY_CLASS,
                        BELOW_NORMAL_PRIORITY_CLASS,
                        IDLE_PRIORITY_CLASS,
                    )

                    Process().nice(NORMAL_PRIORITY_CLASS)
                except ModuleNotFoundError:
                    pass

            except SubprocessError:
                raise WVSExecutionError()

            return True

    def http_log(self):
        """ returns the name of the HTTP log if there is one """
        for filename in glob.glob(os.path.join(self.get_filepath("http_log_*.txt"))):
            return filename

    def has_crashed(self):
        """Returns True if there is any indication that WVSC.EXE has crashed."""
        if self.is_alive():
            return False
        elif self.exit_code != WVSExitCode.NO_ERROR:
            return True
        else:
            return False

    def ctrl_c(self):
        """Sends CTRL-C signal to WVSC.EXE and saves the time of the call."""
        if self._process is None:
            raise WVSProcessError("Process not started")
        self._process.send_signal(subprocess.signal.CTRL_BREAK_EVENT)
        self._ctrl_c_time = time.monotonic()

    def ctrl_c_time_passed(self, seconds):
        """Returns True if seconds already passed from the last ctrl_c call"""
        if self._ctrl_c_time is None:
            raise WVSProcessError("Ctrl-C not sent yet")
        return time.monotonic() - self._ctrl_c_time > seconds

    def ctrl_break(self):
        """Sends CTRL-BREAK signal to WVSC.EXE and saves the time of the call."""
        if self._process is None:
            raise WVSProcessError("Process not started")
        self._process.send_signal(subprocess.signal.CTRL_BREAK_EVENT)
        self._ctrl_break_time = time.monotonic()

    def ctrl_break_time_passed(self, seconds):
        """Returns True if seconds already passed from the last ctrl_break call"""
        if self._ctrl_break_time is None:
            raise WVSProcessError("Ctrl-Break not sent yet")
        return time.monotonic() - self._ctrl_break_time > seconds

    def wait(self, timeout):
        """Waits for WVSC.EXE to end for timeout seconds"""
        if self._process is None:
            raise WVSProcessError("Process not started")
        if self.exit_code is not None:
            return True
            try:
                self.exit_code = self._process.poll()
                if self.exit_code is not None:
                    _module_log.debug("WVS process finished")
                    _, stderr = self._process.communicate(timeout=timeout)
                    if stderr is not None:
                        self.stderr = stderr.decode("utf-8").strip()
                    return True
            except TimeoutExpired:
                return False

    def trace(self):
        """Makes a trace of the WVSC.EXE process and saves it in the working_directory"""
        pass

    def kill(self):
        """Terminates the process"""
        self.trace()
        self._process.terminate()

    def is_alive(self):
        """Returns True if WVSC.EXE is running"""
        if self._process is None:
            return False
        else:
            return not self.wait(0.1)
